package com.yzd.learnjava.concurrency;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import com.yzd.learnjava.util.Print;

public class CarBuilder {
	
	public static void main(String[] args) throws InterruptedException {
		CarQueue chassisQueue = new CarQueue();
		CarQueue finishingQueue = new CarQueue();
		ExecutorService exec = Executors.newCachedThreadPool();
		RobotPool robotPool = new RobotPool();
		exec.execute(new EngineRobot(robotPool));
		exec.execute(new DriverTrainRobot(robotPool));
		exec.execute(new WheelRobot(robotPool));
		exec.execute(new Assembler(chassisQueue, finishingQueue, robotPool));
		exec.execute(new Reporter(finishingQueue));
		exec.execute(new ChassisBuilder(chassisQueue));
		TimeUnit.SECONDS.sleep(7);
		exec.shutdownNow();
	}
}

class Car5{
	private final int id ;
	private boolean engine = false ;
	private boolean driveTrain = false ;
	private boolean wheels = false ;
	public Car5(int idn ) {
		this.id = idn;
	}
	public synchronized int getId() {
		return id;
	}
	public synchronized void addEngine() {
		engine = true;
	}
	public synchronized void addDriveTrain() {
		driveTrain = true;
	}
	public synchronized void addWheels() {
		wheels = true;
	}
	public synchronized String toString() {
		return "Car " + id + " [ engine: " + engine + " driveTrain: " + driveTrain + " wheels: " + wheels + " ]";
	}
}

class CarQueue extends LinkedBlockingQueue<Car5>{}

class ChassisBuilder implements Runnable{
	private CarQueue carQueue;
	private int counter = 0;
	public ChassisBuilder(CarQueue cq) {
		this.carQueue = cq;
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				TimeUnit.MILLISECONDS.sleep(500);
				Car5 c = new Car5(counter++);
				Print.print("ChassisBuilder created " + c );
				carQueue.add(c);
			}
		} catch (InterruptedException e) {
			Print.print("Interrupted : ChassisBuilder");
		}
		Print.print("ChassisBuilder off");
	}
}

class Assembler implements Runnable{
	private CarQueue chassisQueue ;
	private CarQueue finishingQueue;
	private Car5 car;
	private CyclicBarrier barrier = new CyclicBarrier(4);
	private RobotPool robotPool;
	public Assembler(CarQueue cq , CarQueue fq , RobotPool rp) {
		this.chassisQueue = cq ;
		this.finishingQueue = fq ;
		this.robotPool = rp;
	}
	public Car5 car() {
		return car;
	}
	public CyclicBarrier barrier() {
		return barrier;
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				car = chassisQueue.take();
				robotPool.hire(EngineRobot.class, this);
				robotPool.hire(DriverTrainRobot.class, this);
				robotPool.hire(WheelRobot.class, this);
				barrier.await();
				finishingQueue.add(car);
			}
		} catch (InterruptedException e) {
			Print.print("exiting assembler via interrupt");
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}
		Print.print("Assembler off");
	}
}

class Reporter implements Runnable{
	private CarQueue carQueue;
	public Reporter(CarQueue cq) {
		this.carQueue = cq;
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				Print.print(carQueue.take());
			}
		} catch (InterruptedException e) {
			Print.print("Exiting Reporter via Interrupted");
		}
		Print.print("Reporter off");
	}
}

abstract class Robot implements Runnable{
	private RobotPool pool;
	public Robot(RobotPool p) {
		this.pool = p;
	}
	protected Assembler assembler;
	public Robot assignAssembler(Assembler assembler) {
		this.assembler = assembler;
		return this;
	}
	private boolean engage = false ;
	public synchronized void engage() {
		engage = true;
		notifyAll();
	}
	abstract protected void performService();
	public void run() {
		try {
			powerDown();
			while(!Thread.interrupted()) {
				performService();
				assembler.barrier().await();
				powerDown();
			}
		} catch (InterruptedException e) {
			Print.print("Exiting " + this + " via interrupt");
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}
	}
	
	private synchronized void powerDown() throws InterruptedException {
		engage = false ;
		assembler = null;
		pool.release(this);
		while(engage == false) {
			wait();
		}
	}
	
	public String toString() {
		return getClass().getName();
	}
}

class EngineRobot extends Robot{
	public EngineRobot(RobotPool p) {
		super(p);
	}
	protected void performService() {
		Print.print(this + " installing engine");
		assembler.car().addEngine();
	}
}

class DriverTrainRobot extends Robot{
	public DriverTrainRobot(RobotPool p) {
		super(p);
	}
	protected void performService() {
		Print.print(this + " installing driveTrain");
		assembler.car().addDriveTrain();
	}
}

class WheelRobot extends Robot{
	public WheelRobot(RobotPool p) {
		super(p);
	}
	protected void performService() {
		Print.print(this + " installing Wheels");
		assembler.car().addWheels();
	}
}

class RobotPool{
	private Set<Robot> pool = new HashSet<Robot>();
	public synchronized void add(Robot r) {
		pool.add(r);
		notifyAll();
	}
	public synchronized void hire(Class<? extends Robot> robotType , Assembler d) throws InterruptedException {
		Robot temp = null;
		for(Robot r : pool) {
			if(r.getClass().equals(robotType)) {
				temp = r;
			}
		}
		if(temp != null) {
			pool.remove(temp);
			temp.assignAssembler(d);
			temp.engage();
			return;
		}
		wait();
		hire(robotType, d);
	}
	
	public synchronized void release(Robot r) {
		add(r);
	}
}



























